
module sdram #(
    parameter tRC = 6,  // 60 两次刷新或两次激活之间的间隔
    parameter tRP = 2,  // 18 预充电到激活的间隔
    parameter tRCD = 2,  // 18 激活到读写之间的间隔
    parameter tDPL = 2,  // 12 数据输入到预充电间隔
    parameter tDAL = 3,  // 30 数据输入到激活/刷新间隔
    parameter tMRD = 2,  // 模式寄存器配置时间
    parameter tREF = 64_000_00,  // 刷新周期时间
    parameter tRAS = 5,  // 激活到预充电
    parameter tCAS = 3'd2,
    parameter CS_NUM = 2,
    parameter PRE_DELAY = 7,
    parameter FORCE_REF_CYCLE = (tRC + 1) * 8192 + 10000
) (
    input clk,
    input clk_ref,
    input rst_n,

    //ar
    input  [4 : 0] s_axi_arid,
    input  [ 31:0] s_axi_araddr,
    input  [7 : 0] s_axi_arlen,
    input  [2 : 0] s_axi_arsize,
    input  [1 : 0] s_axi_arburst,
    input          s_axi_arlock,
    input  [3 : 0] s_axi_arcache,
    input  [2 : 0] s_axi_arprot,
    input          s_axi_arvalid,
    output         s_axi_arready,
    //r
    output [4 : 0] s_axi_rid,
    output [ 31:0] s_axi_rdata,
    output [1 : 0] s_axi_rresp,
    output         s_axi_rlast,
    output         s_axi_rvalid,
    input          s_axi_rready,
    //aw
    input  [4 : 0] s_axi_awid,
    input  [ 31:0] s_axi_awaddr,
    input  [7 : 0] s_axi_awlen,
    input  [2 : 0] s_axi_awsize,
    input  [1 : 0] s_axi_awburst,
    input          s_axi_awlock,
    input  [3 : 0] s_axi_awcache,
    input  [2 : 0] s_axi_awprot,
    input          s_axi_awvalid,
    output         s_axi_awready,
    //w
    input  [ 31:0] s_axi_wdata,
    input  [3 : 0] s_axi_wstrb,
    input          s_axi_wlast,
    input          s_axi_wvalid,
    output         s_axi_wready,
    //b
    output [4 : 0] s_axi_bid,
    output [1 : 0] s_axi_bresp,
    output         s_axi_bvalid,
    input          s_axi_bready,

    output [12:0] dram_addr,
    output [ 1:0] dram_ba,
    output        dram_cas_n,
    output        dram_cke,
    output        dram_clk,
    output [ 1:0] dram_cs_n,
    output        dram_ras_n,
    output        dram_we_n,
    inout  [31:0] dram_dq,
    output [ 1:0] dram_dqml,
    output [ 1:0] dram_dqmh,
    output        init_end
);

    typedef enum {
        IDLE,
        INIT,
        ACTIVE,
        MRS,
        PRE,
        PRE_ALL,
        READ,
        WRITE,
        REFRESH,
        NOP
    } state_t;
    state_t state;
    state_t nop_state_n;

    wire state_idle = state == IDLE;

    logic init_cmd_pre, init_cmd_ref, init_cmd_mrs, init_cs;

    logic arready, awready;
    logic        wr;
    logic        wready;
    logic [ 2:0] mrs_len;
    logic        wlast;
    logic        bvalid;
    logic [ 4:0] id;
    logic [ 4:0] bid;
    logic [ 4:0] len;
    logic [ 3:0] len_p;
    logic [ 1:0] size;
    logic [15:0] row_bank;
    logic [ 9:0] col;
    logic [ 2:0] counter;
    logic        rdqmv;
    logic [ 3:0] rdqm_len;
    logic        act_valid;
    logic [ 1:0] act_bank;
    logic        act_cs;
    logic [12:0] ref_counter;
    logic [22:0] ref_cycle_counter;
    logic [tCAS-1:0] rwait;
    wire         counter_z = counter == 0;
    wire         ref_counter_z = ref_counter == 0;
    wire         force_refresh = ref_cycle_counter < FORCE_REF_CYCLE;

    logic [ 2:0] remain_cnt;
    logic [ 2:0] rlen;
    logic [31:0] wdata;
    logic [ 3:0] wmask;
    logic        wvalid;

    wire         we_valid = ~bvalid & s_axi_awvalid & ~force_refresh;
    wire         re_valid = ~s_axi_rvalid & s_axi_arvalid & ~force_refresh;
    wire  [ 2:0] addr_len = we_valid ? s_axi_awlen[2:0] : re_valid ? s_axi_arlen[2:0] : 0;
    wire         mrs_cfg = mrs_len != addr_len;
    wire         rdata_hsk = s_axi_rvalid & s_axi_rready;
    wire  [15:0] rw_row_bank = we_valid ? s_axi_awaddr[27:12] : re_valid ? s_axi_araddr[27:12] : 0;
    wire         bank_pre = act_valid & (row_bank != rw_row_bank);
    assign len_p = len == 3 ? 3'd2 : 3'd0;

    wire cmd_pre = init_cmd_pre | (state == PRE);
    wire cmd_ref = init_cmd_ref | (state == REFRESH);
    wire cmd_mrs = init_cmd_mrs;
    wire cmd_act = (state == ACTIVE);
    wire cmd_read = (state == READ);
    wire cmd_write = (state == WRITE) & wvalid;

    assign dram_cke = 1'b1;
    assign dram_clk = clk_ref;
    assign dram_cs_n = ~init_end ? {init_cs, ~init_cs} : {act_cs, ~act_cs};
    assign dram_ras_n = ~cmd_act & ~cmd_pre & ~cmd_ref & ~cmd_mrs;
    assign dram_cas_n = ~cmd_read & ~cmd_write & ~cmd_ref & ~cmd_mrs;
    assign dram_we_n  = ~cmd_write & ~cmd_pre & ~cmd_mrs;

    assign dram_ba = act_bank & {2{~cmd_mrs}};
    assign dram_addr = {
        {2{cmd_act}} & row_bank[12:11],
        (cmd_act) & row_bank[10] | init_cmd_pre | (state == PRE_ALL),
        {10{cmd_mrs}} & {1'b0, 2'b0, tCAS, 1'b0, 3'b0} |
                        {10{cmd_act}} & row_bank[9:0] |
                        {10{cmd_read || cmd_write}} & col
    };
    assign {dram_dqmh, dram_dqml} = {4{cmd_write}} & ~wmask;
    assign dram_dq = cmd_write ? wdata : {32{1'bz}};

    always_ff @(posedge clk) begin
        if (~rst_n) begin
            state       <= INIT;
            arready     <= 1'b0;
            awready     <= 1'b0;
            wready      <= 1'b0;
            wr          <= 1'b0;
            len         <= 2;
            size        <= 0;
            id          <= 0;
            counter     <= 0;
            nop_state_n <= IDLE;
            mrs_len     <= 0;
            id          <= 0;
            wlast       <= 1'b0;
            bvalid      <= 1'b0;
            bid         <= 0;
            rdqmv       <= 0;
            rdqm_len    <= 0;
        end else begin
            if (~counter_z) begin
                counter <= counter - 1;
            end
            if (state_idle & re_valid & ~we_valid) begin
                arready <= 1'b1;
            end else if (s_axi_arvalid & arready) begin
                arready <= 1'b0;
            end
            if (state_idle & we_valid) begin
                awready <= 1'b1;
            end else if (we_valid & awready) begin
                awready <= 1'b0;
            end
            if (we_valid & awready) begin
                wready <= 1'b1;
            end else if (s_axi_wvalid & s_axi_wready & s_axi_wlast) begin
                wready <= 1'b0;
            end
            if (we_valid & (state == IDLE)) begin
                wlast <= 1'b0;
            end else if (s_axi_wvalid & s_axi_wready & s_axi_wlast) begin
                wlast <= 1'b1;
            end
            if (state == WRITE && !wvalid && wlast) begin
                bvalid <= 1'b1;
                bid    <= id;
            end else if (s_axi_bready & bvalid) begin
                bvalid <= 1'b0;
            end
            if ((state == IDLE) & mrs_cfg & (re_valid | we_valid)) begin
                mrs_len <= addr_len;
            end
            if (cmd_write | cmd_read) begin
                col <= col + 1;
            end
            else if(!force_refresh & we_valid)begin
                col <= s_axi_awaddr[11:2];
            end
            else if(!force_refresh & re_valid)begin
                col <= s_axi_araddr[11:2];
            end

            if (state == NOP && counter_z && nop_state_n == READ) begin
                rdqmv <= 1;
            end else if (rdqm_len == 1) begin
                rdqmv <= 0;
            end
            if (state == NOP && counter_z && nop_state_n == READ) begin
                rdqm_len <= len;
            end else if (rdqmv) begin
                rdqm_len <= rdqm_len - 1;
            end

            case (state)
                INIT: if (init_end) state <= IDLE;
                IDLE: begin
                    if (we_valid) begin
                        wr       <= 1'b1;
                        len      <= s_axi_awlen[2:0];
                        size     <= s_axi_awsize;
                        row_bank <= s_axi_awaddr[27:12];
                        id       <= s_axi_awid;
                    end else if (re_valid) begin
                        wr       <= 1'b0;
                        len      <= s_axi_arlen[2:0];
                        size     <= s_axi_arsize;
                        row_bank <= s_axi_araddr[27:12];
                        id       <= s_axi_arid;
                    end
                    if (force_refresh) begin
                        state <= PRE_ALL;
                        nop_state_n <= REFRESH;
                        counter <= 1;
                    end
                    if (re_valid | we_valid) begin
                        state <= ACTIVE;
                    end
                end
                NOP: begin
                    if (counter_z) state <= nop_state_n;
                end
                PRE: begin
                    state   <= NOP;
                    counter <= tRP - 2;
                    nop_state_n <= IDLE;
                end
                PRE_ALL: begin
                    if(act_cs)begin
                        state   <= NOP;
                        counter <= tRP - 2;
                        nop_state_n <= REFRESH;
                    end
                end
                ACTIVE: begin
                    if (wr) begin
                        nop_state_n <= WRITE;
                    end else begin
                        nop_state_n <= READ;
                    end
                    state   <= NOP;
                    counter <= tRCD - 1;
                end
                MRS: begin
                    nop_state_n <= ACTIVE;
                    state <= NOP;
                    counter <= tMRD;
                end
                READ: begin
                    len <= len - 1;
                    if(len == 0)begin
                        state <= NOP;
                        nop_state_n <= PRE;
                        counter <= tCAS - 1;
                    end
                end
                WRITE: begin
                    if (!wvalid && wlast) begin
                        state   <= NOP;
                        nop_state_n  <= PRE;
                        counter <= tDPL - 2;
                    end
                end
                REFRESH: begin
                    if(act_cs)begin
                        if ((ref_counter_z | s_axi_arvalid | s_axi_awvalid) & ~force_refresh) begin
                            nop_state_n <= IDLE;
                        end else begin
                            nop_state_n <= REFRESH;
                        end
                        state   <= NOP;
                        counter <= tRC - 2 - (CS_NUM - 1);
                    end
                end
            endcase
        end
    end

    always_ff @(posedge clk) begin
        if (~rst_n) begin
            act_valid <= 1'b0;
            act_bank  <= 0;
            act_cs    <= 0;
        end else begin
            if (state == ACTIVE) begin
                act_valid <= 1'b1;
            end else if (state == PRE) begin
                act_valid <= 1'b0;
            end
            if((state == IDLE) && force_refresh)begin
                act_cs <= 1'b0;
            end
            if((state == PRE_ALL) || (state == REFRESH))begin
                act_cs <= ~act_cs;
            end
            if((state == IDLE) & (s_axi_arvalid | s_axi_awvalid) & ~force_refresh)begin
                act_bank <= rw_row_bank[14:13];
                act_cs   <= rw_row_bank[15];
            end else if (state == PRE) begin
                act_bank <= row_bank[14:13];
                act_cs   <= row_bank[15];
            end
        end
    end

    always_ff @(posedge clk) begin
        if (~rst_n) begin
            ref_counter <= 8191;
            ref_cycle_counter <= tREF - 1;
        end else begin
            if (ref_cycle_counter != 0 && init_end) begin
                ref_cycle_counter <= ref_cycle_counter - 1;
            end else begin
                ref_cycle_counter <= tREF - 1;
            end

            if (ref_cycle_counter == 0) begin
                ref_counter <= 8191;
            end
            if (state == REFRESH) begin
                ref_counter <= ref_counter - 1;
            end
        end
    end

    sdram_init #(tRP, tRC, tMRD) u_sdram_init (
        .*,
        .cs(init_cs),
        .cmd_pre(init_cmd_pre),
        .cmd_ref(init_cmd_ref),
        .cmd_mrs(init_cmd_mrs)
    );

    always_ff @(posedge clk_ref)begin
        if(~rst_n)begin
            rwait <= 0;
        end
        else begin
            rwait <= {cmd_read, rwait[tCAS-1:1]};
        end
    end

    sdram_async_fifo #(4, 32) u_sdram_rfifo (
        .src_clk(clk_ref),
        .src_rst_n(rst_n),
        .src_wdata(dram_dq),
        .src_valid(rwait[0]),
        .dst_clk(clk),
        .dst_rst_n(rst_n),
        .dst_rdata(s_axi_rdata),
        .dst_valid(s_axi_rvalid),
        .dst_ready(s_axi_rready)
    );
    // sdram_fifo #(4, 32) u_sdram_rfifo (
    //     .clk(clk),
    //     .rst_n(rst_n),
    //     .we(rwait[0]),
    //     .wdata(dram_dq),
    //     .remain_cnt(remain_cnt),
    //     .re(rdata_hsk),
    //     .rvalid(s_axi_rvalid),
    //     .rdata(s_axi_rdata)
    // );

    wire [2:0] wremain_cnt;
    sdram_fifo #(4, 36) u_sdram_wfifo (
        .clk(clk),
        .rst_n(rst_n),
        .we(s_axi_wvalid & s_axi_wready),
        .wdata({s_axi_wstrb, s_axi_wdata}),
        .remain_cnt(wremain_cnt),
        .re((state == WRITE) & wvalid),
        .rvalid(wvalid),
        .rdata({wmask, wdata})
    );

    always_ff @(posedge clk) begin
        if (~rst_n) begin
            rlen <= 0;
        end else begin
            if (re_valid & ~s_axi_awvalid & (state == IDLE)) begin
                rlen <= s_axi_arlen[2:0];
            end else if (rdata_hsk) begin
                rlen <= rlen - 1;
            end
        end
    end

    assign s_axi_arready = arready;

    assign s_axi_rid    = id;
    assign s_axi_rlast  = rlen == 0;
    assign s_axi_rresp  = 0;

    assign s_axi_awready = awready;
    assign s_axi_wready  = wready;

    assign s_axi_bvalid = bvalid;
    assign s_axi_bid    = bid;
    assign s_axi_bresp  = 0;

endmodule
